﻿using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;

namespace Silky.Core.Utils
{
    public static class IPUtil
    {
        static IPUtil()
        {
            LocalIPV4 = Dns.GetHostEntry(Dns.GetHostName()).AddressList.First(x => x.AddressFamily == AddressFamily.InterNetwork);
            LocalIPV4AddressNum = LocalIPV4.ToString().ToIpNum();
        }

        public static IPAddress LocalIPV4 { get; private set; }

        public static long LocalIPV4AddressNum { get; private set; }

        public static bool IsExternalIPAddress(string url)
        {
            var uri = new Uri(url);
            switch (uri.HostNameType)
            {
                case UriHostNameType.Dns:
                    var ipHostEntry = Dns.GetHostEntryAsync(uri.DnsSafeHost).Result;
                    foreach (IPAddress ipAddress in ipHostEntry.AddressList)
                    {
                        if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
                        {
                            if (!IsPrivateIP(ipAddress))
                            {
                                return true;
                            }
                        }
                    }
                    break;

                case UriHostNameType.IPv4:
                    return !IsPrivateIP(IPAddress.Parse(uri.DnsSafeHost));
            }
            return false;
        }

        public static bool IsPrivateIP(IPAddress myIPAddress)
        {
            if (IPAddress.IsLoopback(myIPAddress)) return true;
            if (myIPAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
            {
                byte[] ipBytes = myIPAddress.GetAddressBytes();
                // 10.0.0.0/24
                if (ipBytes[0] == 10)
                {
                    return true;
                }
                // 172.16.0.0/16
                else if (ipBytes[0] == 172 && ipBytes[1] == 16)
                {
                    return true;
                }
                // 192.168.0.0/16
                else if (ipBytes[0] == 192 && ipBytes[1] == 168)
                {
                    return true;
                }
                // 169.254.0.0/16
                else if (ipBytes[0] == 169 && ipBytes[1] == 254)
                {
                    return true;
                }
            }
            return false;
        }

        public static bool IsInnerIP(this string ipAddress)
        {
            bool isInnerIp;
            var ipNum = ToIpNum(ipAddress);

            //Private IP：
            //category A: 10.0.0.0-10.255.255.255
            //category B: 172.16.0.0-172.31.255.255
            //category C: 192.168.0.0-192.168.255.255

            var aBegin = ToIpNum("10.0.0.0");
            var aEnd = ToIpNum("10.255.255.255");
            var bBegin = ToIpNum("172.16.0.0");
            var bEnd = ToIpNum("172.31.255.255");
            var cBegin = ToIpNum("192.168.0.0");
            var cEnd = ToIpNum("192.168.255.255");
            isInnerIp = IsInner(ipNum, aBegin, aEnd) || IsInner(ipNum, bBegin, bEnd) || IsInner(ipNum, cBegin, cEnd);
            return isInnerIp;
        }

        public static long ToIpNum(this string ipAddress)
        {
            try
            {
                if (string.IsNullOrWhiteSpace(ipAddress)) return -1;
                var ip = ipAddress.Split('.');
                long a = int.Parse(ip[0]);
                long b = int.Parse(ip[1]);
                long c = int.Parse(ip[2]);
                long d = int.Parse(ip[3]);

                var ipNum = a * 256 * 256 * 256 + b * 256 * 256 + c * 256 + d;
                return ipNum;
            }
            catch
            {
                return -1;
            }
        }

        public static string ToIpAdress(this long ipNum)
        {
            if (ipNum < 0) return "";
            long a = ipNum % (256 * 256 * 256);
            long b = (ipNum - a * 256 * 256 * 256) % (256 * 256);
            long c = (ipNum - a * 256 * 256 * 256 - b * 256 * 256) % 256;
            long d = ipNum - a * 256 * 256 * 256 - b * 256 * 256 - c * 256;
            return $"{a}.{b}.{c}.{d}";
        }

        public static bool IsInner(long userIp, long begin, long end)
        {
            return userIp >= begin && userIp <= end;
        }
    }
}